package com.ccc.boot.security.config;

import com.ccc.boot.constant.CommonConstant;
import com.ccc.boot.exception.BusinessException;
import com.ccc.boot.log.DBLog;
import com.ccc.boot.log.ILogService;
import com.ccc.boot.log.LogInfo;
import com.ccc.boot.response.ResponseCode;
import com.ccc.boot.security.cache.TokenCache;
import com.ccc.boot.security.constant.AuthConstant;
import com.ccc.boot.security.pojo.AuthUserData;
import com.ccc.boot.security.pojo.AuthorityData;
import com.ccc.boot.security.util.AuthUtils;
import com.ccc.boot.system.entity.SysApi;
import com.ccc.boot.system.service.SysApiService;
import com.ccc.boot.util.LogUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 认证TOKEN过滤器验证用户信息和JWT令牌
 *
 * @author superC
 * @version 0.1.0
 * @date 2022/3/25
 */

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    protected static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Autowired
    private AuthUtils authUtils;

    @Autowired
    private TokenCache tokenCache;

    @Resource(name = "logService")
    private ILogService logService;

    @Autowired
    private SysApiService sysApiService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        /**
         * 忽略登录相关接口认证（若后续添加新接口需要忽略认证，在该处需要进行过滤相应接口）
         */
        if (request.getServletPath().startsWith(AuthConstant.LOGIN_URI_PREFIX)) {
            filterChain.doFilter(request, response);
            return;
        }

        /**
         * 获取token
         */
        String token = "";
        try {
            token = authUtils.getToken(request);
        } catch (BusinessException e) {
            logger.error("【捕获异常】\r\n异常记录：{}", e.getMessage());
            outMsg(response, e);
            return;
        } catch (Exception e) {
            logger.error("【捕获异常】\r\n异常记录：", e);
            outMsg(response, new BusinessException(ResponseCode.GET_TOKEN_ERROR));
            return;
        }
        // 验证token是否存在
        if (StringUtils.isEmpty(token)) {
            outMsg(response, new BusinessException(ResponseCode.TOKEN_ISNULL_ERROR));
            return;
        }

        AuthUserData user = null;
        try {
            /**
             * 验证TOKEN是否过期
             */
            Boolean tokenExpired = authUtils.isAccessTokenExpired(token);
            // token过期
            if (tokenExpired) {
                Object result = null;
                /**
                 * 判断redis中是否存储
                 *      存在则进行刷新生成新的token;
                 *      不存在则用户需重新登录
                 */
                if (tokenCache.isExist(token)) {
                    // 更新缓存中token, 并返回前端新的token
                    result = tokenCache.refreshToken(token);
                }
                outMsg(response, new BusinessException(ResponseCode.TOKEN_EXPIRED_ERROR), result);
                return;
            }

            /**
             * 判断当前token在redis中是否存在
             */
            if (!tokenCache.isExist(token)) {
                outMsg(response, new BusinessException(ResponseCode.TOKEN_IS_INVALID));
                return;
            }

            /**
             * 获取认证用户信息
             */
            user = authUtils.getLoginUser(token);
            if (user == null) {
                outMsg(response, new BusinessException(ResponseCode.PARSE_TOKEN_ERROR));
                return;
            }

        } catch (ExpiredJwtException e) {
            outMsg(response, new BusinessException(ResponseCode.TOKEN_EXPIRED_ERROR));
        } catch (SignatureException e) {
            outMsg(response, new BusinessException(ResponseCode.TOKEN_SIGNATURE_ERROR));
        } catch (IllegalArgumentException e) {
            outMsg(response, new BusinessException(ResponseCode.TOKEN_ISNULL_ERROR));
        } catch (Exception e) {
            logger.error("【捕获异常】\r\n异常记录：", e);
            outMsg(response, new BusinessException(ResponseCode.PARSE_TOKEN_ERROR));
        }

        try {
            /**
             * 权限校验
             */
            checkUserPermission(request, user);

            // 在Security上下文存储用户信息
            authUtils.storageAuthentication(request, user);

            // 调用下一个过滤器
            filterChain.doFilter(request, response);

        } catch (BusinessException e) {
            logger.error("【捕获异常】\r\n异常记录：{}", e.getMessage());
            outMsg(response, e);
        } catch (Exception e) {
            logger.error("【捕获异常】\r\n异常记录：", e);
            outMsg(response, new BusinessException(ResponseCode.GET_RESOURCE_ERROR));
        }
        return;
    }

    private void checkUserPermission(HttpServletRequest request, AuthUserData user) {

        /*
        判断当前请求是否已授权
         */
        // 获取当前用户所有已授权的请求资源
        Set<String> roles = user.getRoles().stream().map(AuthorityData::getRoleName).collect(Collectors.toSet());
        if (roles.isEmpty()) {
            throw new BusinessException(ResponseCode.USER_NO_ROLE);
        }

        /*
        据角色集,获取授权的按钮接口资源数据
         */
        String rolesStr = String.join(CommonConstant.SEPARATOR_COMMA, roles);
        List<SysApi> apiList = sysApiService.getApisByRoles(rolesStr);
        if (apiList == null || apiList.size() == 0) {
//            throw new BusinessException(ResponseCode.ROLE_NO_PERMISSION);
            return;
        }

        /*
        匹配当前请求是否在权限内
         */
        String requestUri = request.getRequestURI();
        String method = request.getMethod();
        // 获取属性——是否记录日志
        boolean isLog = false;
        for (SysApi sysApi : apiList) {
            if (sysApi == null || StringUtils.isEmpty(sysApi.getApiUri()) || StringUtils.isEmpty(sysApi.getApiMethod())) {
                continue;
            }
            String apiUri = sysApi.getApiUri().replaceAll(CommonConstant.CHAR_ASTERISK, "");
            if (requestUri.contains(apiUri) && method.equals(sysApi.getApiMethod())) {
                isLog = sysApi.getLog() == 1;
                break;
            }
        }

        /*
        记录操作日志
         */
        if (isLog) {
            LogInfo logInfo = LogUtils.getOperateLog(request, user.getUsername());
            DBLog.getInstance().setLogService(logService).offerQueue(logInfo);
        }

    }

    private void outMsg(HttpServletResponse response, BusinessException businessException) {
        outMsg(response, businessException, null);
    }

    private void outMsg(HttpServletResponse response, BusinessException businessException, Object data) {
        try {
            response.setContentType("application/json;charset=utf-8");
            // 因为我们使用的REST API,所以我们认为到达后台的请求都是正常的，所以返回的HTTP状态码都是200
            response.setStatus(200);
            PrintWriter out = response.getWriter();
            Map<String, Object> map = new HashMap<>(4);
            // 用接口返回的code来确定请求是否正常
            map.put("code", businessException.getCode());
            map.put("message", businessException.getMessage());
            if (data != null) {
                map.put("data", data);
            }

            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error("【捕获异常】\r\n异常记录：", e);
        }
    }
}